package com.sneakxy.mybatis.commons.repository.query.executor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;
import org.apache.ibatis.scripting.xmltags.WhereSqlNode;
import org.apache.ibatis.session.Configuration;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Slice;

import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.repository.query.domain.Criteria;
import com.sneakxy.mybatis.commons.repository.query.domain.CriteriaSqlNode;
import com.sneakxy.mybatis.commons.repository.query.parser.MybatisPartTree;
import com.sneakxy.mybatis.commons.utils.MybatisUtils;

/**
 * @author 潜行的青衣
 */
public class CriteriaQueryRepsoitoryExecutor<T> extends AbstractRepositoryExecutor<T> {

	private boolean pageQuery;
	
	private boolean countQuery;
	
	private boolean collectionQuery;
	
	private String countStatement;
	
	/**
	 * @param entityManager
	 * @param loader
	 * @param method
	 * @param type
	 */
	public CriteriaQueryRepsoitoryExecutor(MybatisEntityManager entityManager, Class<?> loader, String method,
			Class<?>... type) {
		super(entityManager, loader, method, type);
	}

	/**
	 * @param entityManager
	 * @param method
	 */
	public CriteriaQueryRepsoitoryExecutor(MybatisEntityManager entityManager, Method method) {
		super(entityManager, method);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.RepositoryExecutor#execute(java.lang.Object[])
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public T execute(Object... parameters) {
		Object parameterObject = this.createParameterObject(parameters, false);
		if(this.isCountQuery()) {
			return this.getEntityManager().getSqlSessionTemplate().selectOne(this.getStatement(), parameterObject);
		} else {
			List<?> results = this.getEntityManager().getSqlSessionTemplate().selectList(getStatement(), parameterObject);
			if(this.isCollectionQuery()) {
				return (T) results;
			} else if(this.isPageQuery()) {
				//
				parameterObject = this.createParameterObject(parameters, true);
				Long total = this.getEntityManager().getSqlSessionTemplate().selectOne(getCountStatement(), parameterObject);
				return (T) new PageImpl(results, MybatisUtils.getPageable(parameters), total);
			} else {
				return (T) (results.size() > 0 ? results.get(0) : null);
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.AbstractRepositoryExecutor#onBuildStatment()
	 */
	@Override
	protected void onBuildStatment() {
		this.onBuildListStatement();
		if(this.isPageQuery()) {
			String statement = StringUtils.remove(this.getStatement(), this.getEntityManager().getCurrentNamespace());
			this.countStatement = this.getEntityManager().getNamespacedId(new StringBuilder().append("count-").append(statement).toString());
			onBuildCountStatment();
		}
	}
	
	protected void onBuildListStatement() {
		Class<?> returnType = this.getMethod().getReturnType();
		StringBuilder sqlBuilder = new StringBuilder();
		Class<?> resultType = null;
		String resultMapName = null;
		if(Long.class.isAssignableFrom(returnType) 
				|| long.class.isAssignableFrom(returnType)) {
			sqlBuilder.append("SELECT COUNT(*) ");
			resultType = Long.class;
			countQuery = true;
		} else {
			sqlBuilder.append("SELECT * ");
			resultType = this.getEntityManager().getEntityInformation().getJavaType();
			resultMapName = resultType.getSimpleName();
			this.pageQuery = Slice.class.isAssignableFrom(returnType);
			this.collectionQuery = Collection.class.isAssignableFrom(returnType);
		}
		sqlBuilder.append("FROM ${").append(MybatisPartTree.GENERIC_NAME_PREFIX).append("2} ");
		WhereSqlNode whereSqlNode = new WhereSqlNode(this.getEntityManager().getConfiguration(), new CriteriaSqlNode(this.getEntityManager().getConfiguration(), getCriteriaIndex()));
		List<SqlNode> contents = new ArrayList<SqlNode>();
		contents.add(new TextSqlNode(sqlBuilder.toString()));
		contents.add(whereSqlNode);
		MixedSqlNode sqlNode = new MixedSqlNode(contents);
		String id = this.getStatement();
		Configuration config = this.getEntityManager().getConfiguration();
		SqlCommandType sqlCommandType  = SqlCommandType.SELECT;
		SqlSource sqlSource = new DynamicSqlSource(config, sqlNode);
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, resultMapName, resultType);
	}
	
	protected void onBuildCountStatment() {
		StringBuilder sqlBuilder = new StringBuilder();
		sqlBuilder.append("SELECT COUNT(*) ");
		sqlBuilder.append("FROM ${").append(MybatisPartTree.GENERIC_NAME_PREFIX).append("2} ");
		WhereSqlNode whereSqlNode = new WhereSqlNode(this.getEntityManager().getConfiguration(), new CriteriaSqlNode(this.getEntityManager().getConfiguration(), getCriteriaIndex()));
		List<SqlNode> contents = new ArrayList<SqlNode>();
		contents.add(new TextSqlNode(sqlBuilder.toString()));
		contents.add(whereSqlNode);
		MixedSqlNode sqlNode = new MixedSqlNode(contents);
		String id = this.getCountStatement();
		Configuration config = this.getEntityManager().getConfiguration();
		SqlCommandType sqlCommandType  = SqlCommandType.SELECT;
		SqlSource sqlSource = new DynamicSqlSource(config, sqlNode);
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Long.class);
	}
	
	public int getCriteriaIndex() {
		int index = 1;
		for(Class<?> type:this.getMethod().getParameterTypes()) {
			if(Criteria.class.isAssignableFrom(type)) {
				break;
			}
			index ++;
		}
		return index;
	}

	public boolean isPageQuery() {
		return pageQuery;
	}

	public boolean isCountQuery() {
		return countQuery;
	}

	public boolean isCollectionQuery() {
		return collectionQuery;
	}

	protected String getCountStatement() {
		return countStatement;
	}

	protected void setCountStatement(String countStatement) {
		this.countStatement = countStatement;
	}

}
